package com.example.kotlinnote.picker

import android.content.Context
import android.graphics.*
import android.os.Build
import android.os.Handler
import android.text.TextPaint
import android.text.TextUtils
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.VelocityTracker
import android.view.View
import android.view.ViewConfiguration
import android.widget.Scroller
import androidx.core.content.ContextCompat
import com.example.kotlinnote.R
import com.example.kotlinnote.picker.IWheelPicker.OnWheelChangeListener
import java.util.*

/**
 * 滚轮选择器
 *
 *
 * WheelPicker
 *
 * @author AigeStudio 2015-12-12
 * @author AigeStudio 2016-06-17
 * 更新项目结构
 *
 *
 * New project structure
 * @version 1.1.0
 */
class WheelPicker @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
    View(context, attrs), IWheelPicker, Runnable {
    //private static final String TAG = WheelPicker.class.getSimpleName();
    private val mHandler = Handler()
    private val mPaint: Paint?
    private val mScroller: Scroller
    private var mTracker: VelocityTracker? = null

    /**
     * Determines whether the current scrolling animation is triggered by touchEvent or setSelectedItemPosition.
     * User added eventListeners will only be fired after touchEvents.
     */
    private var mTouchTriggered = false

    /**
     * 相关监听器
     *
     * @see OnWheelChangeListener,OnItemSelectedListener
     */
    private var mOnItemSelectedListener: IWheelPicker.OnItemSelectedListener? = null
    private var mOnWheelChangeListener: OnWheelChangeListener? = null
    private val mRectDrawn: Rect
    private val mRectIndicatorHead: Rect
    private val mRectIndicatorFoot: Rect
    private val mRectCurrentItem: Rect
    private val mCamera: Camera
    private val mMatrixRotate: Matrix
    private val mMatrixDepth: Matrix

    /**
     * 数据源
     */
    private var mData: List<*>? = null

    /**
     * 数据前缀
     */
    private var mPrefix: String? = null

    /**
     * 数据后缀
     */
    private var mSuffix: String? = null

    /**
     * 最宽的文本
     *
     * @see .setMaximumWidthText
     */
    private var mMaxWidthText: String?

    /**
     * 滚轮选择器中可见的数据项数量和滚轮选择器将会绘制的数据项数量
     *
     * @see .setVisibleItemCount
     */
    private var mVisibleItemCount: Int
    private var mDrawnItemCount = 0

    /**
     * 滚轮选择器将会绘制的Item数量的一半
     */
    private var mHalfDrawnItemCount = 0

    /**
     * 单个文本最大宽高
     */
    private var mTextMaxWidth = 0
    private var mTextMaxHeight = 0

    /**
     * 数据项文本颜色以及被选中的数据项文本颜色
     *
     * @see .setItemTextColor
     * @see .setSelectedItemTextColor
     */
    private var mItemTextColor: Int
    private var mSelectedItemTextColor: Int

    /**
     * 数据项文本尺寸
     *
     * @see .setItemTextSize
     */
    private var mItemTextSize: Int

    /**
     * 指示器尺寸
     *
     * @see .setIndicatorSize
     */
    private var mIndicatorSize: Int

    /**
     * 指示器颜色
     *
     * @see .setIndicatorColor
     */
    private var mIndicatorColor: Int

    /**
     * 幕布颜色
     *
     * @see .setCurtainColor
     */
    private var mCurtainColor: Int

    /**
     * 数据项之间间距
     *
     * @see .setItemSpace
     */
    private var mItemSpace: Int

    /**
     * 数据项对齐方式
     *
     * @see .setItemAlign
     */
    private var mItemAlign: Int

    /**
     * 文字的显示效果
     *
     * @see .setTextEllipsize
     */
    private var mTextEllipsize: Int

    /**
     * 滚轮选择器单个数据项高度以及单个数据项一半的高度
     */
    private var mItemHeight = 0
    private var mHalfItemHeight = 0

    /**
     * 滚轮选择器内容区域高度的一半
     */
    private var mHalfWheelHeight = 0

    /**
     * 当前被选中的数据项所显示的数据在数据源中的位置
     *
     * @see .setSelectedItemPosition
     */
    private var mSelectedItemPosition: Int

    /**
     * 当前被选中的数据项所显示的数据在数据源中的位置
     *
     * @see .getCurrentItemPosition
     */
    override var currentItemPosition = 0
        private set

    /**
     * 滚轮滑动时可以滑动到的最小/最大的Y坐标
     */
    private var mMinFlingY = 0
    private var mMaxFlingY = 0

    /**
     * 滚轮滑动时的最小/最大速度
     */
    private var mMinimumVelocity = 50
    private var mMaximumVelocity = 8000

    /**
     * 滚轮选择器中心坐标
     */
    private var mWheelCenterX = 0
    private var mWheelCenterY = 0

    /**
     * 滚轮选择器绘制中心坐标
     */
    private var mDrawnCenterX = 0
    private var mDrawnCenterY = 0

    /**
     * 滚轮选择器视图区域在Y轴方向上的偏移值
     */
    private var mScrollOffsetY = 0

    /**
     * 滚轮选择器中最宽或最高的文本在数据源中的位置
     */
    private var mTextMaxWidthPosition: Int

    /**
     * 用户手指上一次触摸事件发生时事件Y坐标
     */
    private var mLastPointY = 0

    /**
     * 手指触摸屏幕时事件点的Y坐标
     */
    private var mDownPointY = 0

    /**
     * 点击与触摸的切换阀值
     */
    private var mTouchSlop = 8

    /**
     * 滚轮选择器的每一个数据项文本是否拥有相同的宽度
     *
     * @see .setSameWidth
     */
    private var mHasSameWidth: Boolean

    /**
     * 是否显示指示器
     *
     * @see .setIndicator
     */
    private var mHasIndicator: Boolean

    /**
     * 是否显示幕布
     *
     * @see .setCurtain
     */
    private var mHasCurtain: Boolean

    /**
     * 是否显示空气感效果
     *
     * @see .setAtmospheric
     */
    private var mHasAtmospheric: Boolean

    /**
     * 数据是否循环展示
     *
     * @see .setCyclic
     */
    private var mIsCyclic: Boolean

    /**
     * 滚轮是否为卷曲效果
     *
     * @see .setCurved
     */
    private var mIsCurved: Boolean

    /**
     * 是否为点击模式
     */
    private var mIsClick = false

    /**
     * 是否为强制结束滑动
     */
    private var mIsForceFinishScroll = false

    /**
     * Font typeface path from assets
     */
    private val mFontPath: String?
    private fun hasData(): Boolean {
        return mData != null && mData!!.size > 0
    }

    private fun getString(obj: Any): String {
        val stringBuffer = StringBuilder()
        if (mPrefix != null) stringBuffer.append(mPrefix)
        stringBuffer.append(obj)
        if (mSuffix != null) stringBuffer.append(mSuffix)
        return stringBuffer.toString()
    }

    private fun updateVisibleItemCount() {
        if (mVisibleItemCount < 2) throw ArithmeticException("Wheel's visible item count can not be less than 2!")

        // 确保滚轮选择器可见数据项数量为奇数
        // Be sure count of visible item is odd number
        if (mVisibleItemCount % 2 == 0) mVisibleItemCount += 1
        mDrawnItemCount = mVisibleItemCount + 2
        mHalfDrawnItemCount = mDrawnItemCount / 2
    }

    private fun computeTextSize() {
        mTextMaxHeight = 0
        mTextMaxWidth = mTextMaxHeight
        if (hasData()) {
            if (mHasSameWidth) {
                mTextMaxWidth = mPaint!!.measureText(getString(mData!![0]!!)).toInt()
            } else if (isPosInRang(mTextMaxWidthPosition)) {
                mTextMaxWidth =
                    mPaint!!.measureText(getString(mData!![mTextMaxWidthPosition]!!)).toInt()
            } else if (!TextUtils.isEmpty(mMaxWidthText)) {
                mTextMaxWidth = mPaint!!.measureText(mMaxWidthText).toInt()
            } else {
                for (obj in mData!!) {
                    val text = getString(obj!!)
                    val width = mPaint!!.measureText(text).toInt()
                    mTextMaxWidth = Math.max(mTextMaxWidth, width)
                }
            }
        }
        val metrics = mPaint!!.fontMetrics
        mTextMaxHeight = (metrics.bottom - metrics.top).toInt()
    }

    private fun updateItemTextAlign() {
        when (mItemAlign) {
            IWheelPicker.ALIGN_LEFT -> mPaint!!.textAlign = Paint.Align.LEFT
            IWheelPicker.ALIGN_RIGHT -> mPaint!!.textAlign = Paint.Align.RIGHT
            else -> mPaint!!.textAlign = Paint.Align.CENTER
        }
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        val modeWidth = MeasureSpec.getMode(widthMeasureSpec)
        val modeHeight = MeasureSpec.getMode(heightMeasureSpec)
        val sizeWidth = MeasureSpec.getSize(widthMeasureSpec)
        val sizeHeight = MeasureSpec.getSize(heightMeasureSpec)

        // 计算原始内容尺寸
        // Correct sizes of original content
        var resultWidth = mTextMaxWidth
        var resultHeight = mTextMaxHeight * mVisibleItemCount + mItemSpace * (mVisibleItemCount - 1)

        // 如果开启弯曲效果则需要重新计算弯曲后的尺寸
        // Correct view sizes again if curved is enable
        if (mIsCurved) {
            resultHeight = (2 * resultHeight / Math.PI).toInt()
        }
        //if (isDebug)
        //    Log.i(TAG, "Wheel's content size is (" + resultWidth + ":" + resultHeight + ")");

        // 考虑内边距对尺寸的影响
        // Consideration padding influence the view sizes
        resultWidth += paddingLeft + paddingRight
        resultHeight += paddingTop + paddingBottom
        //if (isDebug)
        //    Log.i(TAG, "Wheel's size is (" + resultWidth + ":" + resultHeight + ")");

        // 考虑父容器对尺寸的影响
        // Consideration sizes of parent can influence the view sizes
        resultWidth = measureSize(modeWidth, sizeWidth, resultWidth)
        resultHeight = measureSize(modeHeight, sizeHeight, resultHeight)
        setMeasuredDimension(resultWidth, resultHeight)
    }

    private fun measureSize(mode: Int, sizeExpect: Int, sizeActual: Int): Int {
        var realSize: Int
        if (mode == MeasureSpec.EXACTLY) {
            realSize = sizeExpect
        } else {
            realSize = sizeActual
            if (mode == MeasureSpec.AT_MOST) realSize = Math.min(realSize, sizeExpect)
        }
        return realSize
    }

    override fun onSizeChanged(w: Int, h: Int, oldW: Int, oldH: Int) {
        // 设置内容区域
        // Set content region
        mRectDrawn[paddingLeft, paddingTop, width - paddingRight] = height - paddingBottom
        //if (isDebug)
        //    Log.i(TAG, "Wheel's drawn rect size is (" + mRectDrawn.width() + ":" +
        //            mRectDrawn.height() + ") and location is (" + mRectDrawn.left + ":" +
        //            mRectDrawn.top + ")");

        // 获取内容区域中心坐标
        // Get the center coordinates of content region
        mWheelCenterX = mRectDrawn.centerX()
        mWheelCenterY = mRectDrawn.centerY()

        // 计算数据项绘制中心
        // Correct item drawn center
        computeDrawnCenter()
        mHalfWheelHeight = mRectDrawn.height() / 2
        mItemHeight = mRectDrawn.height() / mVisibleItemCount
        mHalfItemHeight = mItemHeight / 2

        // 初始化滑动最大坐标
        // Initialize fling max Y-coordinates
        computeFlingLimitY()

        // 计算指示器绘制区域
        // Correct region of indicator
        computeIndicatorRect()

        // 计算当前选中的数据项区域
        // Correct region of current select item
        computeCurrentItemRect()
    }

    private fun computeDrawnCenter() {
        when (mItemAlign) {
            IWheelPicker.ALIGN_LEFT -> mDrawnCenterX = mRectDrawn.left
            IWheelPicker.ALIGN_RIGHT -> mDrawnCenterX = mRectDrawn.right
            else -> mDrawnCenterX = mWheelCenterX
        }
        mDrawnCenterY = (mWheelCenterY - (mPaint!!.ascent() + mPaint.descent()) / 2).toInt()
    }

    private fun computeFlingLimitY() {
        if (hasData()) {
            val currentItemOffset = mSelectedItemPosition * mItemHeight
            mMinFlingY =
                if (mIsCyclic) Int.MIN_VALUE else -mItemHeight * (itemCount - 1) + currentItemOffset
            mMaxFlingY = if (mIsCyclic) Int.MAX_VALUE else currentItemOffset
        }
    }

    private fun computeIndicatorRect() {
        if (!mHasIndicator) return
        val halfIndicatorSize = mIndicatorSize / 2
        val indicatorHeadCenterY = mWheelCenterY + mHalfItemHeight
        val indicatorFootCenterY = mWheelCenterY - mHalfItemHeight
        mRectIndicatorHead[mRectDrawn.left, indicatorHeadCenterY - halfIndicatorSize, mRectDrawn.right] =
            indicatorHeadCenterY + halfIndicatorSize
        mRectIndicatorFoot[mRectDrawn.left, indicatorFootCenterY - halfIndicatorSize, mRectDrawn.right] =
            indicatorFootCenterY + halfIndicatorSize
    }

    private fun computeCurrentItemRect() {
        if (!mHasCurtain && mSelectedItemTextColor == -1) return
        mRectCurrentItem[mRectDrawn.left, mWheelCenterY - mHalfItemHeight, mRectDrawn.right] =
            mWheelCenterY + mHalfItemHeight
    }

    override fun onDraw(canvas: Canvas) {
        if (null != mOnWheelChangeListener) mOnWheelChangeListener!!.onWheelScrolled(mScrollOffsetY)
        if (hasData()) {
            val drawnDataStartPos = -mScrollOffsetY / mItemHeight - mHalfDrawnItemCount
            var drawnDataPos = drawnDataStartPos + mSelectedItemPosition
            var drawnOffsetPos = -mHalfDrawnItemCount
            while (drawnDataPos < drawnDataStartPos + mSelectedItemPosition + mDrawnItemCount) {
                var data = ""
                if (mIsCyclic && itemCount > 0) {
                    var actualPos = drawnDataPos % itemCount
                    actualPos = if (actualPos < 0) actualPos + itemCount else actualPos
                    data = getString(mData!![actualPos]!!)
                } else {
                    if (isPosInRang(drawnDataPos)) data = getString(mData!![drawnDataPos]!!)
                }
                mPaint!!.color = mItemTextColor
                mPaint.style = Paint.Style.FILL
                val mDrawnItemCenterY =
                    mDrawnCenterY + drawnOffsetPos * mItemHeight + mScrollOffsetY % mItemHeight
                var distanceToCenter = 0
                if (mIsCurved) {
                    // 计算数据项绘制中心距离滚轮中心的距离比率
                    // Correct ratio of item's drawn center to wheel center
                    val ratio = (mDrawnCenterY - Math.abs(mDrawnCenterY - mDrawnItemCenterY) -
                            mRectDrawn.top) * 1.0f / (mDrawnCenterY - mRectDrawn.top)

                    // 计算单位
                    // Correct unit
                    var unit = 0
                    if (mDrawnItemCenterY > mDrawnCenterY) unit =
                        1 else if (mDrawnItemCenterY < mDrawnCenterY) unit = -1
                    var degree = -(1 - ratio) * 90 * unit
                    if (degree < -90) degree = -90f
                    if (degree > 90) degree = 90f
                    distanceToCenter = computeSpace(degree.toInt())
                    var transX = mWheelCenterX
                    when (mItemAlign) {
                        IWheelPicker.ALIGN_LEFT -> transX = mRectDrawn.left
                        IWheelPicker.ALIGN_RIGHT -> transX = mRectDrawn.right
                    }
                    val transY = mWheelCenterY - distanceToCenter
                    mCamera.save()
                    mCamera.rotateX(degree)
                    mCamera.getMatrix(mMatrixRotate)
                    mCamera.restore()
                    mMatrixRotate.preTranslate(-transX.toFloat(), -transY.toFloat())
                    mMatrixRotate.postTranslate(transX.toFloat(), transY.toFloat())
                    mCamera.save()
                    mCamera.translate(0f, 0f, computeDepth(degree.toInt()).toFloat())
                    mCamera.getMatrix(mMatrixDepth)
                    mCamera.restore()
                    mMatrixDepth.preTranslate(-transX.toFloat(), -transY.toFloat())
                    mMatrixDepth.postTranslate(transX.toFloat(), transY.toFloat())
                    mMatrixRotate.postConcat(mMatrixDepth)
                }
                if (mHasAtmospheric) {
                    var alpha = ((mDrawnCenterY - Math.abs(mDrawnCenterY - mDrawnItemCenterY)) *
                            1.0f / mDrawnCenterY * 255).toInt()
                    alpha = if (alpha < 0) 0 else alpha
                    mPaint.alpha = alpha
                }
                // 根据卷曲与否计算数据项绘制Y方向中心坐标
                // Correct item's drawn centerY base on curved state
                val drawnCenterY =
                    if (mIsCurved) mDrawnCenterY - distanceToCenter else mDrawnItemCenterY

                // 判断是否需要为当前数据项绘制不同颜色
                // Judges need to draw different color for current item or not
                if (mSelectedItemTextColor != -1) {
                    canvas.save()
                    if (mIsCurved) canvas.concat(mMatrixRotate)
                    canvas.clipRect(mRectCurrentItem, Region.Op.DIFFERENCE)
                    canvas.drawText(
                        computeDecentString(data), mDrawnCenterX.toFloat(), drawnCenterY.toFloat(),
                        mPaint
                    )
                    canvas.restore()
                    mPaint.color = mSelectedItemTextColor
                    canvas.save()
                    if (mIsCurved) canvas.concat(mMatrixRotate)
                    canvas.clipRect(mRectCurrentItem)
                    canvas.drawText(
                        computeDecentString(data), mDrawnCenterX.toFloat(), drawnCenterY.toFloat(),
                        mPaint
                    )
                    canvas.restore()
                } else {
                    canvas.save()
                    canvas.clipRect(mRectDrawn)
                    if (mIsCurved) canvas.concat(mMatrixRotate)
                    canvas.drawText(
                        computeDecentString(data), mDrawnCenterX.toFloat(), drawnCenterY.toFloat(),
                        mPaint
                    )
                    canvas.restore()
                }
                drawnDataPos++
                drawnOffsetPos++
            }
            // 是否需要绘制幕布
            // Need to draw curtain or not
            if (mHasCurtain) {
                mPaint!!.color = mCurtainColor
                mPaint.style = Paint.Style.FILL
                canvas.drawRect(mRectCurrentItem, mPaint)
            }
            // 是否需要绘制指示器
            // Need to draw indicator or not
            if (mHasIndicator) {
                mPaint!!.color = mIndicatorColor
                mPaint.style = Paint.Style.FILL
                canvas.drawRect(mRectIndicatorHead, mPaint)
                canvas.drawRect(mRectIndicatorFoot, mPaint)
            }
            //if (isDebug) {
            //    mPaint.setColor(0x4433EE33);
            //    mPaint.setStyle(Paint.Style.FILL);
            //    canvas.drawRect(0, 0, getPaddingLeft(), getHeight(), mPaint);
            //    canvas.drawRect(0, 0, getWidth(), getPaddingTop(), mPaint);
            //    canvas.drawRect(getWidth() - getPaddingRight(), 0, getWidth(), getHeight(), mPaint);
            //    canvas.drawRect(0, getHeight() - getPaddingBottom(), getWidth(), getHeight(), mPaint);
            //}
        }
    }

    /**
     * 计算合适的字符串
     */
    private fun computeDecentString(data: String): String {
        if (TextUtils.isEmpty(data)) return ""
        when (mTextEllipsize) {
            IWheelPicker.END -> return TextUtils.ellipsize(
                data,
                TextPaint(mPaint),
                (width - paddingLeft - paddingRight).toFloat(),
                TextUtils.TruncateAt.END
            ).toString()
            IWheelPicker.MARQUEE -> return TextUtils.ellipsize(
                data,
                TextPaint(mPaint),
                (width - paddingLeft - paddingRight).toFloat(),
                TextUtils.TruncateAt.MARQUEE
            ).toString()
            IWheelPicker.MIDDLE -> return TextUtils.ellipsize(
                data,
                TextPaint(mPaint),
                (width - paddingLeft - paddingRight).toFloat(),
                TextUtils.TruncateAt.MIDDLE
            ).toString()
            IWheelPicker.START -> return TextUtils.ellipsize(
                data,
                TextPaint(mPaint),
                (width - paddingLeft - paddingRight).toFloat(),
                TextUtils.TruncateAt.START
            ).toString()
            else -> return data
        }
    }

    private fun isPosInRang(position: Int): Boolean {
        return hasData() && position >= 0 && position < itemCount
    }

    private fun computeSpace(degree: Int): Int {
        return (Math.sin(Math.toRadians(degree.toDouble())) * mHalfWheelHeight).toInt()
    }

    private fun computeDepth(degree: Int): Int {
        return (mHalfWheelHeight - Math.cos(Math.toRadians(degree.toDouble())) * mHalfWheelHeight).toInt()
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {
                mTouchTriggered = true
                if (null != parent) parent.requestDisallowInterceptTouchEvent(true)
                if (null == mTracker) mTracker = VelocityTracker.obtain() else mTracker!!.clear()
                mTracker!!.addMovement(event)
                if (!mScroller.isFinished) {
                    mScroller.abortAnimation()
                    mIsForceFinishScroll = true
                }
                run {
                    mLastPointY = event.getY().toInt()
                    mDownPointY = mLastPointY
                }
            }
            MotionEvent.ACTION_MOVE -> {
                if (Math.abs(mDownPointY - event.y) < mTouchSlop) {
                    mIsClick = true
                } else {
                    mIsClick = false
                    mTracker!!.addMovement(event)
                    if (null != mOnWheelChangeListener) mOnWheelChangeListener!!.onWheelScrollStateChanged(
                        IWheelPicker.SCROLL_STATE_DRAGGING
                    )

                    // 滚动内容
                    // Scroll WheelPicker's content
                    val move = event.y - mLastPointY
                    if (Math.abs(move) >= 1) {
                        mScrollOffsetY += move.toInt()
                        mLastPointY = event.y.toInt()
                        invalidate()
                    }

                }
            }
            MotionEvent.ACTION_UP -> {
                if (null != parent) parent.requestDisallowInterceptTouchEvent(false)
                if (mIsClick && !mIsForceFinishScroll) return true
                mTracker!!.addMovement(event)
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) mTracker!!.computeCurrentVelocity(
                    1000,
                    mMaximumVelocity.toFloat()
                ) else mTracker!!.computeCurrentVelocity(1000)

                // 根据速度判断是该滚动还是滑动
                // Judges the WheelPicker is scroll or fling base on current velocity
                mIsForceFinishScroll = false
                val velocity = mTracker!!.yVelocity.toInt()
                if (Math.abs(velocity) > mMinimumVelocity) {
                    mScroller.fling(0, mScrollOffsetY, 0, velocity, 0, 0, mMinFlingY, mMaxFlingY)
                    mScroller.finalY = mScroller.finalY +
                            computeDistanceToEndPoint(mScroller.finalY % mItemHeight)
                } else {
                    mScroller.startScroll(
                        0, mScrollOffsetY, 0,
                        computeDistanceToEndPoint(mScrollOffsetY % mItemHeight)
                    )
                }
                // 校正坐标
                // Correct coordinates
                if (!mIsCyclic) if (mScroller.finalY > mMaxFlingY) mScroller.finalY =
                    mMaxFlingY else if (mScroller.finalY < mMinFlingY) mScroller.finalY =
                    mMinFlingY
                mHandler.post(this)
                if (null != mTracker) {
                    mTracker!!.recycle()
                    mTracker = null
                }
            }
            MotionEvent.ACTION_CANCEL -> {
                if (null != parent) parent.requestDisallowInterceptTouchEvent(false)
                if (null != mTracker) {
                    mTracker!!.recycle()
                    mTracker = null
                }
            }
        }
        return true
    }

    private fun computeDistanceToEndPoint(remainder: Int): Int {
        if (Math.abs(remainder) > mHalfItemHeight) return if (mScrollOffsetY < 0) -mItemHeight - remainder else mItemHeight - remainder else return -remainder
    }

    override fun run() {
        if (!hasData()) {
            computeFlingLimitY()
            requestLayout()
            invalidate()
            return
        }
        if (mScroller.isFinished && !mIsForceFinishScroll) {
            if (mItemHeight == 0) return
            var position = (-mScrollOffsetY / mItemHeight + mSelectedItemPosition) % itemCount
            position = if (position < 0) position + itemCount else position
            currentItemPosition = position
            if (null != mOnItemSelectedListener && mTouchTriggered) mOnItemSelectedListener!!.onItemSelected(
                this,
                mData!![position], position
            )
            if (null != mOnWheelChangeListener && mTouchTriggered) {
                mOnWheelChangeListener!!.onWheelSelected(position)
                mOnWheelChangeListener!!.onWheelScrollStateChanged(IWheelPicker.SCROLL_STATE_IDLE)
            }
        }
        if (mScroller.computeScrollOffset()) {
            if (null != mOnWheelChangeListener) mOnWheelChangeListener!!.onWheelScrollStateChanged(
                IWheelPicker.SCROLL_STATE_SCROLLING
            )
            mScrollOffsetY = mScroller.currY
            postInvalidate()
            mHandler.postDelayed(this, 16)
        }
    }

    //@Override
    //public void setDebug(boolean isDebug) {
    //    this.isDebug = isDebug;
    //}
    override var visibleItemCount: Int
        get() = mVisibleItemCount
        set(count) {
            mVisibleItemCount = count
            updateVisibleItemCount()
            requestLayout()
        }

    override var isCyclic: Boolean
        get() = mIsCyclic
        set(isCyclic) {
            mIsCyclic = isCyclic
            computeFlingLimitY()
            invalidate()
        }

    override fun setOnItemSelectedListener(listener: IWheelPicker.OnItemSelectedListener?) {
        mOnItemSelectedListener = listener
    }

    override var selectedItemPosition: Int
        get() = mSelectedItemPosition
        set(position) {
            setSelectedItemPosition(position, false)
        }

    fun setSelectedItemPosition(position: Int, animated: Boolean) {
        var position = position
        if (!hasData()) {
            computeFlingLimitY()
            requestLayout()
            invalidate()
            return
        }
        mTouchTriggered = false
        if (animated && mScroller.isFinished) { // We go non-animated regardless of "animated" parameter if scroller is in motion
            val length = data!!.size
            var itemDifference = position - currentItemPosition
            if (itemDifference == 0) return
            if (mIsCyclic && Math.abs(itemDifference) > length / 2) { // Find the shortest path if it's cyclic
                itemDifference += if (itemDifference > 0) -length else length
            }
            mScroller.startScroll(0, mScroller.currY, 0, -itemDifference * mItemHeight)
            mHandler.post(this)
        } else {
            if (!mScroller.isFinished) mScroller.abortAnimation()
            position = Math.min(position, itemCount - 1)
            position = Math.max(position, 0)
            mSelectedItemPosition = position
            currentItemPosition = position
            mScrollOffsetY = 0
            computeFlingLimitY()
            requestLayout()
            invalidate()
        }
    }
    //if (null == data)
    //    throw new NullPointerException("WheelPicker's data can not be null!");

    // 重置位置
    override var data: List<*>?
        get() = mData
        set(data) {
            //if (null == data)
            //    throw new NullPointerException("WheelPicker's data can not be null!");
            mData = data

            // 重置位置
            val itemCount = itemCount
            if (mSelectedItemPosition > itemCount - 1 || currentItemPosition > itemCount - 1) {
                currentItemPosition = itemCount - 1
                mSelectedItemPosition = currentItemPosition
            } else {
                mSelectedItemPosition = currentItemPosition
            }
            mScrollOffsetY = 0
            computeTextSize()
            computeFlingLimitY()
            requestLayout()
            invalidate()
            setSelectedItemPosition(mSelectedItemPosition, false)
        }

    override val itemCount: Int
        get() = if (mData == null) 0 else mData!!.size

    override fun setSameWidth(hasSameWidth: Boolean) {
        mHasSameWidth = hasSameWidth
        computeTextSize()
        requestLayout()
        invalidate()
    }

    override fun hasSameWidth(): Boolean {
        return mHasSameWidth
    }

    override fun setOnWheelChangeListener(listener: OnWheelChangeListener?) {
        mOnWheelChangeListener = listener
    }

    override var maximumWidthText: String?
        get() = mMaxWidthText
        set(text) {
            if (null == text) throw NullPointerException("Maximum width text can not be null!")
            mMaxWidthText = text
            computeTextSize()
            requestLayout()
            invalidate()
        }
    override var maximumWidthTextPosition: Int
        get() = mTextMaxWidthPosition
        set(position) {
            if (!isPosInRang(position)) throw ArrayIndexOutOfBoundsException(
                "Maximum width text Position must in [0, " +
                        itemCount + "), but current is " + position
            )
            mTextMaxWidthPosition = position
            computeTextSize()
            requestLayout()
            invalidate()
        }
    override var selectedItemTextColor: Int
        get() = mSelectedItemTextColor
        set(color) {
            mSelectedItemTextColor = color
            computeCurrentItemRect()
            invalidate()
        }
    override var itemTextColor: Int
        get() = mItemTextColor
        set(color) {
            mItemTextColor = color
            invalidate()
        }
    override var itemTextSize: Int
        get() = mItemTextSize
        set(size) {
            mItemTextSize = size
            mPaint!!.textSize = mItemTextSize.toFloat()
            computeTextSize()
            requestLayout()
            invalidate()
        }
    override var itemSpace: Int
        get() = mItemSpace
        set(space) {
            mItemSpace = space
            requestLayout()
            invalidate()
        }

    override fun setIndicator(hasIndicator: Boolean) {
        mHasIndicator = hasIndicator
        computeIndicatorRect()
        invalidate()
    }

    override fun hasIndicator(): Boolean {
        return mHasIndicator
    }

    override var indicatorSize: Int
        get() = mIndicatorSize
        set(size) {
            mIndicatorSize = size
            computeIndicatorRect()
            invalidate()
        }
    override var indicatorColor: Int
        get() = mIndicatorColor
        set(color) {
            mIndicatorColor = color
            invalidate()
        }

    override fun setCurtain(hasCurtain: Boolean) {
        mHasCurtain = hasCurtain
        computeCurrentItemRect()
        invalidate()
    }

    override fun hasCurtain(): Boolean {
        return mHasCurtain
    }

    override var curtainColor: Int
        get() = mCurtainColor
        set(color) {
            mCurtainColor = color
            invalidate()
        }

    override fun setAtmospheric(hasAtmospheric: Boolean) {
        mHasAtmospheric = hasAtmospheric
        invalidate()
    }

    override fun hasAtmospheric(): Boolean {
        return mHasAtmospheric
    }

    override var isCurved: Boolean
        get() = mIsCurved
        set(isCurved) {
            mIsCurved = isCurved
            requestLayout()
            invalidate()
        }
    override var itemAlign: Int
        get() = mItemAlign
        set(align) {
            mItemAlign = align
            updateItemTextAlign()
            computeDrawnCenter()
            invalidate()
        }

    override fun setTextEllipsize(ellipsize: Int) {
        mTextEllipsize = ellipsize
        computeTextSize()
        requestLayout()
        invalidate()
    }

    override var typeface: Typeface?
        get() {
            return if (null != mPaint) mPaint.typeface else null
        }
        set(tf) {
            if (null != mPaint) mPaint.typeface = tf
            computeTextSize()
            requestLayout()
            invalidate()
        }

    override fun setPrefix(prefix: String?) {
        mPrefix = prefix
    }

    override fun setSuffix(suffix: String?) {
        mSuffix = suffix
    }

    init {
        val a = context.obtainStyledAttributes(attrs, R.styleable.WheelPicker)
        val idData = a.getResourceId(R.styleable.WheelPicker_widget_wheel_data, 0)
        if (idData != 0) mData = Arrays.asList(*resources.getStringArray(idData))
        mItemTextSize = a.getDimensionPixelSize(
            R.styleable.WheelPicker_widget_wheel_item_text_size,
            resources.getDimensionPixelSize(R.dimen.widget_wheel_item_text_size)
        )
        mVisibleItemCount = a.getInt(
            R.styleable.WheelPicker_widget_wheel_visible_item_count,
            context.resources.getInteger(R.integer.widget_wheel_visible_item_count)
        )
        mSelectedItemPosition =
            a.getInt(R.styleable.WheelPicker_widget_wheel_selected_item_position, 0)
        mHasSameWidth = a.getBoolean(R.styleable.WheelPicker_widget_wheel_same_width, false)
        mTextMaxWidthPosition =
            a.getInt(R.styleable.WheelPicker_widget_wheel_maximum_width_text_position, -1)
        mMaxWidthText = a.getString(R.styleable.WheelPicker_widget_wheel_maximum_width_text)
        mSelectedItemTextColor = a.getColor(
            R.styleable.WheelPicker_widget_wheel_selected_item_text_color,
            ContextCompat.getColor(context, R.color.widget_wheel_selected_item_text_color)
        )
        mItemTextColor = a.getColor(
            R.styleable.WheelPicker_widget_wheel_item_text_color,
            ContextCompat.getColor(context, R.color.widget_wheel_item_text_color)
        )
        mItemSpace = a.getDimensionPixelSize(
            R.styleable.WheelPicker_widget_wheel_item_space,
            resources.getDimensionPixelSize(R.dimen.widget_wheel_item_space)
        )
        mIsCyclic = a.getBoolean(R.styleable.WheelPicker_widget_wheel_cyclic, false)
        mHasIndicator = a.getBoolean(R.styleable.WheelPicker_widget_wheel_indicator, false)
        mIndicatorColor = a.getColor(
            R.styleable.WheelPicker_widget_wheel_indicator_color,
            ContextCompat.getColor(context, R.color.widget_wheel_indicator_color)
        )
        mIndicatorSize = a.getDimensionPixelSize(
            R.styleable.WheelPicker_widget_wheel_indicator_size,
            resources.getDimensionPixelSize(R.dimen.widget_wheel_indicator_size)
        )
        mHasCurtain = a.getBoolean(R.styleable.WheelPicker_widget_wheel_curtain, false)
        mCurtainColor = a.getColor(
            R.styleable.WheelPicker_widget_wheel_curtain_color,
            ContextCompat.getColor(context, R.color.widget_wheel_curtain_color)
        )
        mHasAtmospheric = a.getBoolean(R.styleable.WheelPicker_widget_wheel_atmospheric, false)
        mIsCurved = a.getBoolean(R.styleable.WheelPicker_widget_wheel_curved, false)
        mItemAlign =
            a.getInt(R.styleable.WheelPicker_widget_wheel_item_align, IWheelPicker.ALIGN_CENTER)
        mTextEllipsize = a.getInt(R.styleable.WheelPicker_widget_wheel_ellipsize, IWheelPicker.NONE)
        mFontPath = a.getString(R.styleable.WheelPicker_widget_wheel_font_path)
        a.recycle()

        // 可见数据项改变后更新与之相关的参数
        // Update relevant parameters when the count of visible item changed
        updateVisibleItemCount()
        mPaint = Paint(Paint.ANTI_ALIAS_FLAG or Paint.DITHER_FLAG or Paint.LINEAR_TEXT_FLAG)
        mPaint.setTextSize(mItemTextSize.toFloat())
        if (mFontPath != null) {
            var typeface = Typeface.createFromAsset(context.assets, mFontPath)
            typeface = typeface
        }

        // 更新文本对齐方式
        // Update alignment of text
        updateItemTextAlign()

        // 计算文本尺寸
        // Correct sizes of text
        computeTextSize()
        mScroller = Scroller(getContext())
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.DONUT) {
            val conf = ViewConfiguration.get(getContext())
            mMinimumVelocity = conf.scaledMinimumFlingVelocity
            mMaximumVelocity = conf.scaledMaximumFlingVelocity
            mTouchSlop = conf.scaledTouchSlop
        }
        mRectDrawn = Rect()
        mRectIndicatorHead = Rect()
        mRectIndicatorFoot = Rect()
        mRectCurrentItem = Rect()
        mCamera = Camera()
        mMatrixRotate = Matrix()
        mMatrixDepth = Matrix()
    }
}